#pragma once

namespace cppwinrt
{
    struct finish_with
    {
        writer& w;
        void (*finisher)(writer&);

        finish_with(writer& w, void (*finisher)(writer&)) : w(w), finisher(finisher) {}
        finish_with(finish_with const&)= delete;
        void operator=(finish_with const&) = delete;

        ~finish_with() { finisher(w); }
    };

    static void write_nothing(writer&)
    {
    }

    static void write_preamble(writer& w)
    {
        if (settings.license)
        {
            w.write(R"(// C++/WinRT v%

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

)", CPPWINRT_VERSION_STRING);
        }
        else
        {
            w.write(R"(// WARNING: Please don't edit this file. It was generated by C++/WinRT v%

)", CPPWINRT_VERSION_STRING);
        }
    }

    static void write_version_assert(writer& w)
    {
        w.write_root_include("base");
        auto format = R"(static_assert(winrt::check_version(CPPWINRT_VERSION, "%"), "Mismatched C++/WinRT headers.");
#define CPPWINRT_VERSION "%"
)";
        w.write(format, CPPWINRT_VERSION_STRING, CPPWINRT_VERSION_STRING);
    }

    static void write_include_guard(writer& w)
    {
        auto format = R"(#pragma once
)";

        w.write(format);
    }

    static void write_endif(writer& w)
    {
        auto format = R"(#endif
)";

        w.write(format);
    }

    static void write_close_file_guard(writer& w)
    {
        write_endif(w);
    }

    static void write_open_file_guard(writer& w, std::string_view const& file_name, char impl = 0)
    {
        std::string mangled_name;

        for (auto&& c : file_name)
        {
            mangled_name += c == '.' ? '_' : c;
        }

        if (impl)
        {
            mangled_name += '_';
            mangled_name += impl;
        }

        auto format = R"(#ifndef WINRT_%_H
#define WINRT_%_H
)";

        w.write(format, mangled_name, mangled_name);
    }

    template<typename... Args>
    [[nodiscard]] static finish_with wrap_open_file_guard(writer& w, Args&&... args)
    {
        write_open_file_guard(w, std::forward<Args>(args)...);
        return { w, write_close_file_guard };
    }

    [[nodiscard]] static finish_with wrap_lean_and_mean(writer& w, bool is_lean_and_mean = true)
    {
        if (is_lean_and_mean)
        {
            auto format = R"(#ifndef WINRT_LEAN_AND_MEAN
)";

            w.write(format);

            return { w, write_endif };
        }
        else
        {
            return { w, write_nothing };
        }
    }

    static void write_parent_depends(writer& w, cache const& c, std::string_view const& type_namespace)
    {
        auto pos = type_namespace.rfind('.');

        if (pos == std::string::npos)
        {
            return;
        }

        auto parent = type_namespace.substr(0, pos);
        auto found = c.namespaces().find(parent);

        if (found != c.namespaces().end() && has_projected_types(found->second))
        {
            w.write_root_include(parent);
        }
        else
        {
            write_parent_depends(w, c, parent);
        }
    }

    static void write_pch(writer& w)
    {
        auto format = R"(#include "%"
)";

        if (!settings.component_pch.empty())
        {
            w.write(format, settings.component_pch);
        }
    }

    static void write_close_namespace(writer& w)
    {
        auto format = R"(}
)";

        w.write(format);
    }

    [[nodiscard]] static finish_with wrap_impl_namespace(writer& w)
    {
        auto format = R"(namespace winrt::impl
{
)";

        w.write(format);

        return { w, write_close_namespace };
    }

    [[nodiscard]] static finish_with wrap_std_namespace(writer& w)
    {
        w.write(R"(namespace std
{
)");

        return { w, write_close_namespace };
    }

    [[nodiscard]] static finish_with wrap_type_namespace(writer& w, std::string_view const& ns)
    {
        auto format = R"(WINRT_EXPORT namespace winrt::@
{
)";

        w.write(format, ns);

        return { w, write_close_namespace };
    }

    static void write_enum_field(writer& w, Field const& field)
    {
        auto format = R"(        % = %,
)";

        if (auto constant = field.Constant())
        {
            w.write(format, field.Name(), *constant);
        }
    }

    static void write_enum(writer& w, TypeDef const& type)
    {
        auto format = R"(    enum class % : %
    {
%    };
)";

        auto fields = type.FieldList();
        w.write(format, type.TypeName(), fields.first.Signature().Type(), bind_each<write_enum_field>(fields));
    }

    static void write_enum_operators(writer& w, TypeDef const& type)
    {
        if (!has_attribute(type, "System", "FlagsAttribute"))
        {
            return;
        }

        auto name = type.TypeName();

        auto format = R"(    constexpr auto operator|(% const left, % const right) noexcept
    {
        return static_cast<%>(impl::to_underlying_type(left) | impl::to_underlying_type(right));
    }
    constexpr auto operator|=(%& left, % const right) noexcept
    {
        left = left | right;
        return left;
    }
    constexpr auto operator&(% const left, % const right) noexcept
    {
        return static_cast<%>(impl::to_underlying_type(left) & impl::to_underlying_type(right));
    }
    constexpr auto operator&=(%& left, % const right) noexcept
    {
        left = left & right;
        return left;
    }
    constexpr auto operator~(% const value) noexcept
    {
        return static_cast<%>(~impl::to_underlying_type(value));
    }
    constexpr auto operator^^(% const left, % const right) noexcept
    {
        return static_cast<%>(impl::to_underlying_type(left) ^^ impl::to_underlying_type(right));
    }
    constexpr auto operator^^=(%& left, % const right) noexcept
    {
        left = left ^^ right;
        return left;
    }
)";

        w.write(format, name, name, name, name, name, name, name, name, name, name, name, name, name, name, name, name, name);
    }

    static void write_generic_typenames(writer& w, std::pair<GenericParam, GenericParam> const& params)
    {
        separator s{ w };

        for (auto&& param : params)
        {
            s();
            w.write("typename %", param);
        }
    }

    static void write_generic_asserts(writer& w, std::pair<GenericParam, GenericParam> const& params)
    {
        for (auto&& param : params)
        {
            auto format = R"(
        static_assert(impl::has_category_v<%>, "% must be WinRT type.");)";

            w.write(format, param, param);
        }
    }

    static void write_comma_generic_typenames(writer& w, std::pair<GenericParam, GenericParam> const& params)
    {
        for (auto&& param : params)
        {
            w.write(", typename %", param);
        }
    }

    static void write_comma_generic_types(writer& w, std::pair<GenericParam, GenericParam> const& params)
    {
        for (auto&& param : params)
        {
            w.write(", %", param);
        }
    }

    static void write_forward(writer& w, TypeDef const& type)
    {
        type_name type_name(type);

        if (get_category(type) == category::enum_type)
        {
            auto format = R"(    enum class % : %;
)";

            w.write(format, type_name.name, type.FieldList().first.Signature().Type());
            return;
        }

        if (type_name == "Windows.Foundation.DateTime" ||
            type_name == "Windows.Foundation.TimeSpan")
        {
            // Don't forward declare these since they're not structs.
            return;
        }

        if (type_name.name_space == "Windows.Foundation.Numerics")
        {
            if (type_name.name == "Matrix3x2" ||
                type_name.name == "Matrix4x4" ||
                type_name.name == "Plane" ||
                type_name.name == "Quaternion" ||
                type_name.name == "Vector2" ||
                type_name.name == "Vector3" ||
                type_name.name == "Vector4")
            {
                // Don't forward declare these since they're already defined with different names.
                return;
            }
        }

        auto generics = type.GenericParam();

        if (empty(generics))
        {
            auto format = R"(    struct %;
)";

            w.write(format, type_name.name);
            return;
        }

        auto format = R"(    template <%> struct __declspec(empty_bases) %;
)";

        w.write(format,
            bind<write_generic_typenames>(generics),
            remove_tick(type_name.name));
    }

    static void write_guid_value(writer& w, std::vector<FixedArgSig> const& args)
    {
        using std::get;

        w.write_printf("0x%08X,0x%04X,0x%04X,{ 0x%02X,0x%02X,0x%02X,0x%02X,0x%02X,0x%02X,0x%02X,0x%02X }",
            get<uint32_t>(get<ElemSig>(args[0].value).value),
            get<uint16_t>(get<ElemSig>(args[1].value).value),
            get<uint16_t>(get<ElemSig>(args[2].value).value),
            get<uint8_t>(get<ElemSig>(args[3].value).value),
            get<uint8_t>(get<ElemSig>(args[4].value).value),
            get<uint8_t>(get<ElemSig>(args[5].value).value),
            get<uint8_t>(get<ElemSig>(args[6].value).value),
            get<uint8_t>(get<ElemSig>(args[7].value).value),
            get<uint8_t>(get<ElemSig>(args[8].value).value),
            get<uint8_t>(get<ElemSig>(args[9].value).value),
            get<uint8_t>(get<ElemSig>(args[10].value).value));
    }

    static void write_guid_comment(writer& w, std::vector<FixedArgSig> const& args)
    {
        using std::get;

        w.write_printf("%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
            get<uint32_t>(get<ElemSig>(args[0].value).value),
            get<uint16_t>(get<ElemSig>(args[1].value).value),
            get<uint16_t>(get<ElemSig>(args[2].value).value),
            get<uint8_t>(get<ElemSig>(args[3].value).value),
            get<uint8_t>(get<ElemSig>(args[4].value).value),
            get<uint8_t>(get<ElemSig>(args[5].value).value),
            get<uint8_t>(get<ElemSig>(args[6].value).value),
            get<uint8_t>(get<ElemSig>(args[7].value).value),
            get<uint8_t>(get<ElemSig>(args[8].value).value),
            get<uint8_t>(get<ElemSig>(args[9].value).value),
            get<uint8_t>(get<ElemSig>(args[10].value).value));
    }

    static void write_category(writer& w, TypeDef const& type, std::string_view const& category)
    {
        auto generics = type.GenericParam();

        if (empty(generics))
        {
            auto format = R"(    template <> struct category<%>{ using type = %; };
)";

            w.write(format, type, category);
        }
        else
        {
            auto format = R"(    template <%> struct category<%>{ using type = generic_category<%>; };
)";

            w.write(format,
                bind<write_generic_typenames>(generics),
                type,
                bind_list(", ", generics));
        }
    }

    static void write_generic_names(writer& w, std::pair<GenericParam, GenericParam> const& params)
    {
        bool first{ true };

        for (auto&& param : params)
        {
            if (first)
            {
                first = false;
            }
            else
            {
                w.write(R"(, L", ")");
            }
            w.write(", name_v<%>", param.Name());
        }
    }

    static void write_name(writer& w, TypeDef const& type)
    {
        type_name type_name(type);
        auto generics = type.GenericParam();

        if (empty(generics))
        {
            auto format = R"(    template <> inline constexpr auto& name_v<%> = L"%.%";
)";

            w.write(format, type, type_name.name_space, type_name.name);
        }
        else
        {
            auto format = R"(    template <%> inline constexpr auto name_v<%> = zcombine(L"%.%<"%, L">");
)";

            w.write(format,
                bind<write_generic_typenames>(generics),
                type,
                type_name.name_space,
                type_name.name,
                bind<write_generic_names>(generics));
        }
    }

    static void write_guid(writer& w, TypeDef const& type)
    {
        auto attribute = get_attribute(type, "Windows.Foundation.Metadata", "GuidAttribute");

        if (!attribute)
        {
            throw_invalid("'Windows.Foundation.Metadata.GuidAttribute' attribute for type '", type.TypeNamespace(), ".", type.TypeName(), "' not found");
        }

        auto generics = type.GenericParam();
        auto guid = attribute.Value().FixedArgs();

        if (empty(generics))
        {
            auto format = R"(    template <> inline constexpr guid guid_v<%>{ % }; // %
)";

            w.write(format,
                type,
                bind<write_guid_value>(guid),
                bind<write_guid_comment>(guid));
        }
        else
        {
            auto format = R"(    template <%> inline constexpr guid guid_v<%>{ pinterface_guid<%>::value };
    template <%> inline constexpr guid generic_guid_v<%>{ % }; // %
)";

            w.write(format,
                bind<write_generic_typenames>(generics),
                type,
                type,
                bind<write_generic_typenames>(generics),
                type,
                bind<write_guid_value>(guid),
                bind<write_guid_comment>(guid));
        }
    }

    static void write_default_interface(writer& w, TypeDef const& type)
    {
        if (auto default_interface = get_default_interface(type))
        {
            auto format = R"(    template <> struct default_interface<%>{ using type = %; };
)";
            w.write(format, type, default_interface);
        }
    }

    static void write_struct_category(writer& w, TypeDef const& type)
    {
        auto format = R"(    template <> struct category<%>{ using type = struct_category<%>; };
)";

        w.write(format, type, bind_list(", ", type.FieldList()));
    }

    static void write_array_size_name(writer& w, Param const& param)
    {
        if (w.param_names)
        {
            w.write(" __%Size", param.Name());
        }
    }

    static void write_abi_arg_in(writer& w, TypeSig const& type)
    {
        if (std::holds_alternative<GenericTypeIndex>(type.Type()))
        {
            w.write("arg_in<%>", type);
        }
        else
        {
            w.write(type);
        }
    }

    static void write_abi_arg_out(writer& w, TypeSig const& type)
    {
        if (std::holds_alternative<GenericTypeIndex>(type.Type()))
        {
            w.write("arg_out<%>", type);
        }
        else
        {
            w.write("%*", type);
        }
    }

    static void write_abi_params(writer& w, method_signature const& method_signature)
    {
        auto abi_guard = w.push_abi_types(true);
        separator s{ w };

        for (auto&& [param, param_signature] : method_signature.params())
        {
            s();

            if (param_signature->Type().is_szarray())
            {
                std::string_view format;

                if (param.Flags().In())
                {
                    format = "uint32_t%, %";
                }
                else if (param_signature->ByRef())
                {
                    format = "uint32_t*%, %*";
                }
                else
                {
                    format = "uint32_t%, %";
                }

                w.write(format, bind<write_array_size_name>(param), bind<write_abi_arg_out>(param_signature->Type()));
            }
            else
            {
                if (param.Flags().In())
                {
                    write_abi_arg_in(w, param_signature->Type());

                    if (is_const(*param_signature))
                    {
                        w.write(" const&");
                    }
                }
                else
                {
                    write_abi_arg_out(w, param_signature->Type());
                }
            }

            if (w.param_names)
            {
                w.write(" %", param.Name());
            }
        }

        if (method_signature.return_signature())
        {
            s();

            auto const& type = method_signature.return_signature().Type();

            if (type.is_szarray())
            {
                w.write("uint32_t* __%Size, %**", method_signature.return_param_name(), type);
            }
            else
            {
                write_abi_arg_out(w, type);
            }

            if (w.param_names)
            {
                w.write(" %", method_signature.return_param_name());
            }
        }
    }

    static void write_abi_args(writer& w, method_signature const& method_signature)
    {
        separator s{ w };

        for (auto&& [param, param_signature] : method_signature.params())
        {
            s();
            auto param_name = param.Name();

            TypeDef signature_type;
            auto category = get_category(param_signature->Type(), &signature_type);

            if (param.Flags().In())
            {
                switch (category)
                {
                case param_category::object_type:
                case param_category::string_type:
                    w.write("*(void**)(&%)", param_name);
                    break;
                case param_category::generic_type:
                case param_category::struct_type:
                    w.write("impl::bind_in(%)", param_name);
                    break;
                case param_category::enum_type:
                    w.write("static_cast<%>(%)", signature_type.FieldList().first.Signature().Type(), param_name);
                    break;
                case param_category::fundamental_type:
                    w.write(param_name);
                    break;
                case param_category::array_type:
                    w.write("%.size(), get_abi(%)", param_name, param_name);
                    break;
                }
            }
            else
            {
                switch (category)
                {
                case param_category::fundamental_type:
                    w.write("&%", param_name);
                    break;
                case param_category::enum_type:
                    w.write("reinterpret_cast<%*>(&%)", signature_type.FieldList().first.Signature().Type(), param_name);
                    break;
                case param_category::array_type:
                    if (param_signature->ByRef())
                    {
                        w.write("impl::put_size_abi(%), put_abi(%)", param_name, param_name);
                    }
                    else
                    {
                        w.write("%.size(), put_abi(%)", param_name, param_name);
                    }
                    break;
                default:
                    w.write("impl::bind_out(%)", param_name);
                    break;
                }
            }
        }

        if (method_signature.return_signature())
        {
            s();
            auto param_name = method_signature.return_param_name();
            TypeDef signature_type;
            auto category = get_category(method_signature.return_signature().Type(), &signature_type);

            if (category == param_category::array_type)
            {
                w.write("&%_impl_size, &%", param_name, param_name);
            }
            else if (category == param_category::struct_type || category == param_category::generic_type)
            {
                w.write("put_abi(%)", param_name);
            }
            else if (category == param_category::enum_type)
            {
                w.write("reinterpret_cast<%*>(&%)", signature_type.FieldList().first.Signature().Type(), param_name);
            }
            else
            {
                w.write("&%", param_name);
            }
        }
    }

    static void write_fast_interface_abi(writer& w, TypeDef const& default_interface)
    {
        if (!settings.fastabi)
        {
            return;
        }

        auto pair = settings.fastabi_cache.find(default_interface);

        if (pair == settings.fastabi_cache.end())
        {
            return;
        }

        auto bases = get_bases(pair->second);

        std::for_each(bases.rbegin(), bases.rend(), [&](auto&& base)
        {
            auto format = R"(            virtual void* __stdcall base_%() noexcept = 0;
)";

            w.write(format, base.TypeName());
        });

        for (auto&& [name, info] : get_interfaces(w, pair->second))
        {
            if (info.is_default)
            {
                continue;
            }
            
            if (!info.fastabi)
            {
                break;
            }

            auto format = R"(            virtual int32_t __stdcall %(%) noexcept = 0;
)";

            for (auto&& method : info.type.MethodList())
            {
                method_signature signature{ method };
                w.write(format, get_abi_name(method), bind<write_abi_params>(signature));
            }
        }
    }

    static void write_interface_abi(writer& w, TypeDef const& type)
    {
        auto generics = type.GenericParam();
        auto guard{ w.push_generic_params(generics) };

        if (empty(generics))
        {
            auto format = R"(    template <> struct abi<%>
    {
        struct __declspec(novtable) type : inspectable_abi
        {
)";

            w.write(format, type);
        }
        else
        {
            auto format = R"(    template <%> struct abi<%>
    {
        struct __declspec(novtable) type : inspectable_abi
        {
)";

            w.write(format,
                bind<write_generic_typenames>(generics),
                type);
        }


        auto format = R"(            virtual int32_t __stdcall %(%) noexcept = 0;
)";

        auto abi_guard = w.push_abi_types(true);
        for (auto&& method : type.MethodList())
        {
            try
            {
                method_signature signature{ method };
                w.write(format, get_abi_name(method), bind<write_abi_params>(signature));
            }
            catch (std::exception const& e)
            {
                throw_invalid(e.what(),
                    "\n method: ", get_name(method),
                    "\n type: ", type.TypeNamespace(), ".", type.TypeName(),
                    "\n database: ", type.get_database().path());
            }
        }

        write_fast_interface_abi(w, type);

        w.write(R"(        };
    };
)");
    }

    static void write_delegate_abi(writer& w, TypeDef const& type)
    {
        auto format = R"(    template <%> struct abi<%>
    {
        struct __declspec(novtable) type : unknown_abi
        {
            virtual int32_t __stdcall Invoke(%) noexcept = 0;
        };
    };
)";

        auto generics = type.GenericParam();
        auto guard{ w.push_generic_params(generics) };
        auto method = get_delegate_method(type);
        method_signature signature{ method };

        w.write(format,
            bind<write_generic_typenames>(generics),
            type,
            bind<write_abi_params>(signature));
    }

    static void write_field_abi(writer& w, Field const& field)
    {
        w.write("        % %;\n", get_field_abi(w, field), field.Name());
    }

    static void write_struct_abi(writer& w, TypeDef const& type)
    {
        auto abi_guard = w.push_abi_types(true);

        auto format = R"(    struct struct_%
    {
%    };
    template <> struct abi<@::%>
    {
        using type = struct_%;
    };
)";

        type_name type_name(type);
        auto impl_name = get_impl_name(type_name.name_space, type_name.name);

        w.write(format,
            impl_name,
            bind_each<write_field_abi>(type.FieldList()),
            type_name.name_space, type_name.name,
            impl_name);

    }

    static void write_consume_params(writer& w, method_signature const& signature)
    {
        separator s{ w };

        for (auto&& [param, param_signature] : signature.params())
        {
            s();

            if (param_signature->Type().is_szarray())
            {
                std::string_view format;

                if (param.Flags().In())
                {
                    format = "array_view<% const>";
                }
                else if (param_signature->ByRef())
                {
                    format = "com_array<%>&";
                }
                else
                {
                    format = "array_view<%>";
                }

                w.write(format, param_signature->Type().Type());
            }
            else
            {
                if (param.Flags().In())
                {
                    assert(!param.Flags().Out());
                    w.consume_types = true;

                    auto param_type = std::get_if<ElementType>(&param_signature->Type().Type());

                    if (param_type && *param_type != ElementType::String && *param_type != ElementType::Object)
                    {
                        w.write("%", param_signature->Type());
                    }
                    else if (std::holds_alternative<GenericTypeIndex>(param_signature->Type().Type()))
                    {
                        w.write("impl::param_type<%> const&", param_signature->Type());
                    }
                    else
                    {
                        w.write("% const&", param_signature->Type());
                    }

                    w.consume_types = false;
                }
                else
                {
                    assert(!param.Flags().In());
                    assert(param.Flags().Out());

                    w.write("%&", param_signature->Type());
                }
            }

            w.write(" %", param.Name());
        }
    }

    static void write_implementation_params(writer& w, method_signature const& method_signature)
    {
        separator s{ w };

        for (auto&& [param, param_signature] : method_signature.params())
        {
            s();

            if (param_signature->Type().is_szarray())
            {
                std::string_view format;

                if (param.Flags().In())
                {
                    format = "array_view<% const>";
                }
                else if (param_signature->ByRef())
                {
                    format = "com_array<%>&";
                }
                else
                {
                    format = "array_view<%>";
                }

                w.write(format, param_signature->Type().Type());
            }
            else
            {
                if (param.Flags().In())
                {
                    assert(!param.Flags().Out());

                    auto param_type = std::get_if<ElementType>(&param_signature->Type().Type());

                    if ((!is_put_overload(method_signature.method()) && w.async_types) ||
                        (param_type && *param_type != ElementType::String && *param_type != ElementType::Object))
                    {
                        w.write("%", param_signature->Type());
                    }
                    else
                    {
                        w.write("% const&", param_signature->Type());
                    }
                }
                else
                {
                    assert(!param.Flags().In());
                    assert(param.Flags().Out());

                    w.write("%&", param_signature->Type());
                }
            }

            w.write(" %", param.Name());
        }
    }

    static void write_consume_declaration(writer& w, MethodDef const& method)
    {
        method_signature signature{ method };
        auto async_types_guard = w.push_async_types(signature.is_async());
        auto method_name = get_name(method);
        auto type = method.Parent();

        w.write("        %WINRT_IMPL_AUTO(%) %(%) const%;\n",
            is_get_overload(method) ? "[[nodiscard]] " : "",
            signature.return_signature(),
            method_name,
            bind<write_consume_params>(signature),
            is_noexcept(method) ? " noexcept" : "");

        if (is_add_overload(method))
        {
            auto format = R"(        using %_revoker = impl::event_revoker<%, &impl::abi_t<%>::remove_%>;
        [[nodiscard]] %_revoker %(auto_revoke_t, %) const;
)";

            w.write(format,
                method_name,
                type,
                type,
                method_name,
                method_name,
                method_name,
                bind<write_consume_params>(signature));
        }
    }

    static void write_fast_consume_declarations(writer& w, TypeDef const& default_interface)
    {
        auto pair = settings.fastabi_cache.find(default_interface);

        if (pair == settings.fastabi_cache.end())
        {
            return;
        }

        for (auto&& [name, info] : get_interfaces(w, pair->second))
        {
            if (info.is_default)
            {
                continue;
            }

            if (!info.fastabi)
            {
                break;
            }

            w.write_each<write_consume_declaration>(info.type.MethodList());
        }
    }

    static void write_consume_return_type(writer& w, method_signature const& signature, bool delegate_types)
    {
        if (!signature.return_signature())
        {
            return;
        }

        auto category = get_category(signature.return_signature().Type());

        if (category == param_category::array_type)
        {
            auto format = R"(
        uint32_t %_impl_size{};
        %* %{};)";

            auto abi_guard = w.push_abi_types(true);
            auto delegate_guard = w.push_delegate_types(delegate_types);

            w.write(format,
                signature.return_param_name(),
                signature.return_signature(),
                signature.return_param_name());
        }
        else if (category == param_category::object_type || category == param_category::string_type)
        {
            auto format = "\n        void* %{};";
            w.write(format, signature.return_param_name());
        }
        else if (category == param_category::generic_type)
        {
            auto format = "\n        % %{ empty_value<%>() };";
            w.write(format, signature.return_signature(), signature.return_param_name(), signature.return_signature());
        }
        else
        {
            auto format = "\n        % %{};";
            w.write(format, signature.return_signature(), signature.return_param_name());
        }
    }

    static void write_consume_return_statement(writer& w, method_signature const& signature)
    {
        if (!signature.return_signature())
        {
            return;
        }

        auto category = get_category(signature.return_signature().Type());

        if (category == param_category::array_type)
        {
            w.write("\n        return %{ %, %_impl_size, take_ownership_from_abi };",
                signature.return_signature(),
                signature.return_param_name(),
                signature.return_param_name());
        }
        else if (category == param_category::object_type || category == param_category::string_type)
        {
            w.write("\n        return %{ %, take_ownership_from_abi };",
                signature.return_signature(),
                signature.return_param_name());
        }
        else
        {
            w.write("\n        return %;", signature.return_param_name());
        }
    }

    static void write_consume_args(writer& w, method_signature const& signature)
    {
        separator s{ w };

        for (auto&& [param, param_signature] : signature.params())
        {
            s();
            w.write(param.Name());
        }
    }

    static void write_consume_definition(writer& w, TypeDef const& type, MethodDef const& method, std::pair<GenericParam, GenericParam> const& generics, std::string_view const& type_impl_name)
    {
        auto method_name = get_name(method);
        method_signature signature{ method };
        auto async_types_guard = w.push_async_types(signature.is_async());

        std::string_view format;

        if (is_noexcept(method))
        {
            format = R"(    template <typename D%> WINRT_IMPL_AUTO(%) consume_%<D%>::%(%) const noexcept
    {%
        WINRT_VERIFY_(0, WINRT_IMPL_SHIM(%)->%(%));%
    }
)";
        }
        else
        {
            format = R"(    template <typename D%> WINRT_IMPL_AUTO(%) consume_%<D%>::%(%) const
    {%
        check_hresult(WINRT_IMPL_SHIM(%)->%(%));%
    }
)";
        }

        w.write(format,
            bind<write_comma_generic_typenames>(generics),
            signature.return_signature(),
            type_impl_name,
            bind<write_comma_generic_types>(generics),
            method_name,
            bind<write_consume_params>(signature),
            bind<write_consume_return_type>(signature, false),
            type,
            get_abi_name(method),
            bind<write_abi_args>(signature),
            bind<write_consume_return_statement>(signature));

        if (is_add_overload(method))
        {
            format = R"(    template <typename D%> typename consume_%<D%>::%_revoker consume_%<D%>::%(auto_revoke_t, %) const
    {
        return impl::make_event_revoker<D, %_revoker>(this, %(%));
    }
)";

            w.write(format,
                bind<write_comma_generic_typenames>(generics),
                type_impl_name,
                bind<write_comma_generic_types>(generics),
                method_name,
                type_impl_name,
                bind<write_comma_generic_types>(generics),
                method_name,
                bind<write_consume_params>(signature),
                method_name,
                method_name,
                bind<write_consume_args>(signature));
        }
    }

    static void write_consume_fast_base_definition(writer& w, MethodDef const& method, TypeDef const& class_type, TypeDef const& base_type)
    {
        auto method_name = get_name(method);
        method_signature signature{ method };
        auto async_types_guard = w.push_async_types(signature.is_async());

        //
        // Note: this use of a lambda is a workaround for a Visual C++ compiler bug:
        // https://developercommunity.visualstudio.com/content/problem/554130/incorrect-code-gen-when-invoking-a-conversion-oper.html
        // Once fixed, revert the function body back to this:
        //
        // return static_cast<% const&>(*this).%(%);
        //

        std::string_view format = R"(    inline WINRT_IMPL_AUTO(%) %::%(%) const%
    {
        return [&](% const& winrt_impl_base) { return winrt_impl_base.%(%); }(*this);
    }
)";

        w.write(format,
            signature.return_signature(),
            class_type.TypeName(),
            method_name,
            bind<write_consume_params>(signature),
            is_noexcept(method) ? " noexcept" : "",
            base_type,
            method_name,
            bind<write_consume_args>(signature));

        if (is_add_overload(method))
        {
            format = R"(    inline %::%_revoker %::%(auto_revoke_t, %) const
    {
        return impl::make_event_revoker<D, %_revoker>(this, %(%));
    }
)";

            w.write(format,
                class_type.TypeName(),
                method_name,
                class_type.TypeName(),
                method_name,
                bind<write_consume_params>(signature),
                method_name,
                method_name,
                bind<write_consume_args>(signature));
        }
    }

    static void write_consume_definitions(writer& w, TypeDef const& type)
    {
        auto generics = type.GenericParam();
        auto guard{ w.push_generic_params(generics) };
        auto type_name = type.TypeName();

        if (!empty(generics))
        {
            type_name = remove_tick(type_name);
        }

        auto type_namespace = type.TypeNamespace();
        auto type_impl_name = get_impl_name(type_namespace, type_name);

        for (auto&& method : type.MethodList())
        {
            write_consume_definition(w, type, method, generics, type_impl_name);
        }

        if (!settings.fastabi)
        {
            return;
        }

        auto pair = settings.fastabi_cache.find(type);

        if (pair == settings.fastabi_cache.end())
        {
            return;
        }

        for (auto&& [name, info] : get_interfaces(w, pair->second))
        {
            if (info.is_default)
            {
                continue;
            }

            if (!info.fastabi)
            {
                break;
            }

            for (auto&& method : info.type.MethodList())
            {
                write_consume_definition(w, type, method, generics, type_impl_name);
            }
        }
    }

    static void write_consume_extensions(writer& w, TypeDef const& type)
    {
        type_name type_name(type);

        if (type_name == "Windows.UI.Xaml.Interop.IBindableIterator")
        {
            w.write(R"(
        auto& operator++()
        {
            if (!MoveNext())
            {
                static_cast<D&>(*this) = nullptr;
            }

            return *this;
        }

        auto operator*() const
        {
            return Current();
        }
)");
        }
        else if (type_name == "Windows.Storage.Streams.IBuffer")
        {
            w.write(R"(
        auto data() const
        {
            uint8_t* data{};
            static_cast<D const&>(*this).template as<IBufferByteAccess>()->Buffer(&data);
            return data;
        }
)");
        }
        else if (type_name == "Windows.Foundation.Collections.IIterator`1")
        {
            w.write(R"(
        auto& operator++()
        {
            if (!MoveNext())
            {
                static_cast<D&>(*this) = nullptr;
            }

            return *this;
        }

        T operator*() const
        {
            return Current();
        }
)");
        }
        else if (type_name == "Windows.Foundation.Collections.IKeyValuePair`2")
        {
            w.write(R"(
        bool operator==(Windows::Foundation::Collections::IKeyValuePair<K, V> const& other) const
        {
            return Key() == other.Key() && Value() == other.Value();
        }

        bool operator!=(Windows::Foundation::Collections::IKeyValuePair<K, V> const& other) const
        {
            return !(*this == other);
        }
)");
        }
        else if (type_name == "Windows.Foundation.Collections.IMapView`2")
        {
            w.write(R"(
        auto TryLookup(param_type<K> const& key) const
        {
            if constexpr (std::is_base_of_v<Windows::Foundation::IUnknown, V>)
            {
                V result{ nullptr };
                impl::check_hresult_allow_bounds(WINRT_IMPL_SHIM(Windows::Foundation::Collections::IMapView<K, V>)->Lookup(get_abi(key), put_abi(result)));
                return result;
            }
            else
            {
                std::optional<V> result;
                V value{ empty_value<V>() };

                if (0 == impl::check_hresult_allow_bounds(WINRT_IMPL_SHIM(Windows::Foundation::Collections::IMapView<K, V>)->Lookup(get_abi(key), put_abi(value))))
                {
                    result = std::move(value);
                }

                return result;
            }
        }
)");
        }
        else if (type_name == "Windows.Foundation.Collections.IMap`2")
        {
            w.write(R"(
        auto TryLookup(param_type<K> const& key) const
        {
            if constexpr (std::is_base_of_v<Windows::Foundation::IUnknown, V>)
            {
                V result{ nullptr };
                impl::check_hresult_allow_bounds(WINRT_IMPL_SHIM(Windows::Foundation::Collections::IMap<K, V>)->Lookup(get_abi(key), put_abi(result)));
                return result;
            }
            else
            {
                std::optional<V> result;
                V value{ empty_value<V>() };

                if (0 == impl::check_hresult_allow_bounds(WINRT_IMPL_SHIM(Windows::Foundation::Collections::IMap<K, V>)->Lookup(get_abi(key), put_abi(value))))
                {
                    result = std::move(value);
                }

                return result;
            }
        }

        auto TryRemove(param_type<K> const& key) const
        {
            return 0 == impl::check_hresult_allow_bounds(WINRT_IMPL_SHIM(Windows::Foundation::Collections::IMap<K, V>)->Remove(get_abi(key)));
        }
)");
        }
        else if (type_name == "Windows.Foundation.IAsyncAction")
        {
            w.write(R"(        auto get() const;
        auto wait_for(Windows::Foundation::TimeSpan const& timeout) const;
)");
        }
        else if (type_name == "Windows.Foundation.IAsyncOperation`1")
        {
            w.write(R"(        auto get() const;
        auto wait_for(Windows::Foundation::TimeSpan const& timeout) const;
)");
        }
        else if (type_name == "Windows.Foundation.IAsyncActionWithProgress`1")
        {
            w.write(R"(        auto get() const;
        auto wait_for(Windows::Foundation::TimeSpan const& timeout) const;
)");
        }
        else if (type_name == "Windows.Foundation.IAsyncOperationWithProgress`2")
        {
            w.write(R"(        auto get() const;
        auto wait_for(Windows::Foundation::TimeSpan const& timeout) const;
)");
        }
    }

    static void write_interface_extensions(writer& w, TypeDef const& type)
    {
        type_name type_name(type);

        if (type_name == "Windows.Foundation.Collections.IIterator`1")
        {
            w.write(R"(
        using iterator_category = std::input_iterator_tag;
        using value_type = T;
        using difference_type = ptrdiff_t;
        using pointer = T*;
        using reference = T&;
)");
        }
        else if (type_name == "Windows.Foundation.IReference`1")
        {
            w.write(R"(        IReference(T const& value) : IReference<T>(impl::reference_traits<T>::make(value))
        {
        }

    private:

        IReference<T>(IInspectable const& value) : IReference<T>(value.as<IReference<T>>())
        {
        }
)");
        }
    }

    static void write_consume(writer& w, TypeDef const& type)
    {
        auto generics = type.GenericParam();
        auto guard{ w.push_generic_params(generics) };
        auto type_name = type.TypeName();

        if (!empty(generics))
        {
            type_name = remove_tick(type_name);
        }

        auto type_namespace = type.TypeNamespace();
        auto impl_name = get_impl_name(type_namespace, type_name);

        if (empty(generics))
        {
            auto format = R"(    template <typename D>
    struct consume_%
    {
%%%    };
    template <> struct consume<%>
    {
        template <typename D> using type = consume_%<D>;
    };
)";


            w.write(format,
                impl_name,
                bind_each<write_consume_declaration>(type.MethodList()),
                bind<write_fast_consume_declarations>(type),
                bind<write_consume_extensions>(type),
                type,
                impl_name);
        }
        else
        {
            auto format = R"(    template <typename D, %>
    struct consume_%
    {
%%%    };
    template <%> struct consume<%>
    {
        template <typename D> using type = consume_%<D, %>;
    };
)";


            w.write(format,
                bind<write_generic_typenames>(generics),
                impl_name,
                bind_each<write_consume_declaration>(type.MethodList()),
                bind<write_fast_consume_declarations>(type),
                bind<write_consume_extensions>(type),
                bind<write_generic_typenames>(generics),
                type,
                impl_name,
                bind_list(", ", generics));
        }
    }

    static void write_produce_params(writer& w, method_signature const& signature)
    {
        write_abi_params(w, signature);
    }

    template <typename T>
    static void write_produce_cleanup_param(writer& w, T const& param_signature, std::string_view const& param_name, bool out)
    {
        TypeSig const& signature = param_signature.Type();
        bool clear{};
        bool optional{};
        bool zero{};

        call(signature.Type(),
            [&](ElementType type)
            {
                if (out && type == ElementType::Object)
                {
                    optional = true;
                }
                else if (type == ElementType::String || type == ElementType::Object)
                {
                    clear = true;
                }
            },
            [&](coded_index<TypeDefOrRef> const& index)
            {
                assert(index.type() == TypeDefOrRef::TypeDef || index.type() == TypeDefOrRef::TypeRef);

                TypeDef type;

                if (index.type() == TypeDefOrRef::TypeDef)
                {
                    type = index.TypeDef();
                }
                else if (index.type() == TypeDefOrRef::TypeRef)
                {
                    type = find(index.TypeRef());
                }

                if (type)
                {
                    auto category = get_category(type);

                    clear = category == category::class_type || category == category::interface_type || category == category::delegate_type;
                    zero = category == category::struct_type;
                }
            },
            [&](GenericTypeIndex const&)
            {
                clear = true;
            },
            [](GenericMethodTypeIndex)
            {
                throw_invalid("Generic methods not supported.");
            },
            [&](GenericTypeInstSig const&)
            {
                clear = true;
            });

        if (signature.is_szarray())
        {
            if constexpr (std::is_same_v<RetTypeSig, T>)
            {
                clear = true;
            }
            else if (param_signature.ByRef())
            {
                clear = true;
            }
            else if (optional || clear)
            {
                clear = false;
                zero = true;
            }
        }

        if (clear)
        {
            auto format = R"(            clear_abi(%);
)";

            w.write(format, param_name);
        }
        else if (zero)
        {
            if (signature.is_szarray())
            {
                auto format = R"(            zero_abi<%>(%, __%Size);
)";

                w.write(format,
                    signature.Type(),
                    param_name,
                    param_name);
            }
            else
            {
                auto format = R"(            zero_abi<%>(%);
)";

                w.write(format,
                    signature.Type(),
                    param_name);
            }
        }
        else if (optional)
        {
            auto format = R"(            if (%) *% = nullptr;
            Windows::Foundation::IInspectable winrt_impl_%;
)";

            w.write(format, param_name, param_name, param_name);
        }
    }

    static void write_produce_cleanup(writer& w, method_signature const& method_signature)
    {
        for (auto&& [param, param_signature] : method_signature.params())
        {
            if (param.Flags().In())
            {
                continue;
            }

            write_produce_cleanup_param(w, *param_signature, param.Name(), true);
        }

        if (method_signature.return_signature())
        {
            write_produce_cleanup_param(w, method_signature.return_signature(), method_signature.return_param_name(), false);
        }
    }

    static void write_produce_args(writer& w, method_signature const& method_signature)
    {
        separator s{ w };

        for (auto&& [param, param_signature] : method_signature.params())
        {
            s();
            auto param_name = param.Name();
            auto param_type = w.write_temp("%", param_signature->Type().Type());

            if (param_signature->Type().is_szarray())
            {
                if (param.Flags().In())
                {
                    w.write("array_view<@ const>(reinterpret_cast<@ const *>(%), reinterpret_cast<@ const *>(%) + __%Size)",
                        param_type,
                        param_type,
                        param_name,
                        param_type,
                        param_name,
                        param_name);
                }
                else if (param_signature->ByRef())
                {
                    w.write("detach_abi<@>(__%Size, %)",
                        param_type,
                        param_name,
                        param_name);
                }
                else
                {
                    w.write("array_view<@>(reinterpret_cast<@*>(%), reinterpret_cast<@*>(%) + __%Size)",
                        param_type,
                        param_type,
                        param_name,
                        param_type,
                        param_name,
                        param_name);
                }
            }
            else
            {
                auto category = get_category(param_signature->Type());

                if (param.Flags().In())
                {
                    if (category != param_category::fundamental_type)
                    {
                        w.write("*reinterpret_cast<% const*>(&%)",
                            param_type,
                            param_name);
                    }
                    else
                    {
                        w.write(param_name);
                    }
                }
                else
                {
                    if (is_object(param_signature->Type()))
                    {
                        w.write("winrt_impl_%", param_name);
                    }
                    else if (category != param_category::fundamental_type)
                    {
                        w.write("*reinterpret_cast<@*>(%)",
                            param_type,
                            param_name);
                    }
                    else
                    {
                        w.write("*%", param_name);
                    }
                }
            }
        }
    }

    static void write_produce_upcall(writer& w, std::string_view const& upcall, method_signature const& method_signature)
    {
        if (method_signature.return_signature())
        {
            auto name = method_signature.return_param_name();

            if (method_signature.return_signature().Type().is_szarray())
            {
                w.write("std::tie(*__%Size, *%) = detach_abi(%(%));",
                    name,
                    name,
                    upcall,
                    bind<write_produce_args>(method_signature));
            }
            else
            {
                w.write("*% = detach_from<%>(%(%));",
                    name,
                    method_signature.return_signature(),
                    upcall,
                    bind<write_produce_args>(method_signature));
            }
        }
        else
        {
            w.write("%(%);",
                upcall,
                bind<write_produce_args>(method_signature));
        }

        for (auto&& [param, param_signature] : method_signature.params())
        {
            if (param.Flags().Out() && !param_signature->Type().is_szarray() && is_object(param_signature->Type()))
            {
                auto param_name = param.Name();

                w.write("\n                if (%) *% = detach_abi(winrt_impl_%);", param_name, param_name, param_name);
            }
        }
    }

    static void write_produce_method(writer& w, MethodDef const& method)
    {
        std::string_view format;

        if (is_noexcept(method))
        {
            format = R"(        int32_t __stdcall %(%) noexcept final
        {
%            typename D::abi_guard guard(this->shim());
            %
            return 0;
        }
)";
        }
        else
        {
            format = R"(        int32_t __stdcall %(%) noexcept final try
        {
%            typename D::abi_guard guard(this->shim());
            %
            return 0;
        }
        catch (...) { return to_hresult(); }
)";
        }

        method_signature signature{ method };
        auto async_types_guard = w.push_async_types(signature.is_async());
        std::string upcall = "this->shim().";
        upcall += get_name(method);

        w.write(format,
            get_abi_name(method),
            bind<write_produce_params>(signature),
            bind<write_produce_cleanup>(signature),
            bind<write_produce_upcall>(upcall, signature));
    }

    static void write_fast_produce_methods(writer& w, TypeDef const& default_interface)
    {
        if (!settings.fastabi)
        {
            return;
        }

        auto pair = settings.fastabi_cache.find(default_interface);

        if (pair == settings.fastabi_cache.end())
        {
            return;
        }

        auto bases = get_bases(pair->second);

        std::for_each(bases.rbegin(), bases.rend(), [&](auto && base)
        {
            auto format = R"(        void* __stdcall base_%() noexcept final
        {
            return this->shim().base_%();
        }
)";

            auto base_name = base.TypeName();
            w.write(format, base_name, base_name);
        });

        for (auto&& [name, info] : get_interfaces(w, pair->second))
        {
            if (info.is_default)
            {
                continue;
            }

            if (!info.fastabi)
            {
                break;
            }

            w.write_each<write_produce_method>(info.type.MethodList());
        }
    }

    static void write_produce(writer& w, TypeDef const& type, cache const& c)
    {
        auto format = R"(    template <typename D%>
    struct produce<D, %> : produce_base<D, %>
    {
%%    };
)";

        auto generics = type.GenericParam();
        auto guard{ w.push_generic_params(generics) };
        bool const lean_and_mean = !can_produce(type, c);

        auto wrap = wrap_lean_and_mean(w, lean_and_mean);

        w.write(format,
            bind<write_comma_generic_typenames>(generics),
            type,
            type,
            bind_each<write_produce_method>(type.MethodList()),
            bind<write_fast_produce_methods>(type));
    }

    static void write_dispatch_overridable_method(writer& w, MethodDef const& method)
    {
        auto format = R"(    auto %(%)
    {
        if (auto overridable = this->shim_overridable())
        {
            return overridable.%(%);
        }

        return this->shim().%(%);
    }
)";

        method_signature signature{ method };

        w.write(format,
            get_name(method),
            bind<write_implementation_params>(signature),
            get_name(method),
            bind<write_consume_args>(signature),
            get_name(method),
            bind<write_consume_args>(signature));
    }

    static void write_dispatch_overridable(writer& w, TypeDef const& class_type)
    {
        auto format = R"(template <typename T, typename D>
struct __declspec(empty_bases) produce_dispatch_to_overridable<T, D, %>
    : produce_dispatch_to_overridable_base<T, D, %>
{
%};
)";

        for (auto&& [interface_name, info] : get_interfaces(w, class_type))
        {
            if (info.overridable && !info.base)
            {
                w.write(format,
                    interface_name,
                    interface_name,
                    bind_each<write_dispatch_overridable_method>(info.type.MethodList()));
            }
        }
    }

    static void write_interface_override_method(writer& w, MethodDef const& method, std::string_view const& interface_name)
    {
        auto format = R"(    template <typename D> WINRT_IMPL_AUTO(%) %T<D>::%(%) const
    {
        return shim().template try_as<%>().%(%);
    }
)";

        method_signature signature{ method };
        auto method_name = get_name(method);

        w.write(format,
            signature.return_signature(),
            interface_name,
            method_name,
            bind<write_consume_params>(signature),
            interface_name,
            method_name,
            bind<write_consume_args>(signature));
    }

    static void write_interface_override_methods(writer& w, TypeDef const& class_type)
    {
        for (auto&& [interface_name, info] : get_interfaces(w, class_type))
        {
            if (info.overridable && !info.base)
            {
                auto name = info.type.TypeName();

                w.write_each<write_interface_override_method>(info.type.MethodList(), name);
            }
        };
    }

    static void write_class_override_implements(writer& w, get_interfaces_t const& interfaces)
    {
        bool found{};

        for (auto&& [name, info] : interfaces)
        {
            if (info.overridable)
            {
                w.write(", %", name);
                found = true;
            }
        }

        if (!found)
        {
            w.write(", Windows::Foundation::IInspectable");
        }
    }

    static void write_class_override_requires(writer& w, get_interfaces_t const& interfaces)
    {
        bool found{};

        for (auto&& [name, info] : interfaces)
        {
            if (!info.overridable)
            {
                w.write(", %", name);
                found = true;
            }
        }
    }

    static void write_class_override_defaults(writer& w, get_interfaces_t const& interfaces)
    {
        bool first{ true };

        for (auto&& [name, info] : interfaces)
        {
            if (!info.overridable)
            {
                continue;
            }

            if (first)
            {
                first = false;
                w.write(",\n        %T<D>", name);
            }
            else
            {
                w.write(", %T<D>", name);
            }
        }
    }

    static void write_class_override_bases(writer& w, TypeDef const& type)
    {
        for (auto&& base : get_bases(type))
        {
            w.write(", %", base);
        }
    }

    static void write_call_factory(writer& w, TypeDef const& type, TypeDef const& factory)
    {
        std::string factory_name;

        if (type.TypeNamespace() == factory.TypeNamespace())
        {
            factory_name = factory.TypeName();
        }
        else
        {
            factory_name = w.write_temp("%", factory);
        }

        auto format = "impl::call_factory<%, %>([&](% const& f)";

        w.write(format,
            type.TypeName(),
            factory_name,
            factory_name);
    }

    static void write_optimized_call_factory(writer& w, TypeDef const& type, TypeDef const& factory, method_signature const& signature)
    {
        std::string factory_name;

        if (type.TypeNamespace() == factory.TypeNamespace())
        {
            factory_name = factory.TypeName();
        }
        else
        {
            factory_name = w.write_temp("%", factory);
        }

        if (signature.params().empty())
        {
            auto format = "impl::call_factory_cast<%(*)(% const&), %, %>([](% const& f) { return f.%(); })";

            w.write(format,
                signature.return_signature(),
                factory_name,
                type.TypeName(),
                factory_name,
                factory_name,
                get_name(signature.method()));
        }
        else
        {
            auto format = "impl::call_factory<%, %>([&](% const& f) { return f.%(%); })";

            w.write(format,
                type.TypeName(),
                factory_name,
                factory_name,
                get_name(signature.method()),
                bind<write_consume_args>(signature));
        }
    }

    static void write_class_override_constructors(writer& w, TypeDef const& type, std::map<std::string, factory_info> const& factories)
    {
        auto type_name = type.TypeName();

        auto format = R"(        %T(%)
        {
            % { [[maybe_unused]] auto winrt_impl_discarded = f.%(%%*this, this->m_inner); });
        }
)";

        for (auto&& [factory_name, factory] : factories)
        {
            if (!factory.composable)
            {
                continue;
            }

            for (auto&& method : factory.type.MethodList())
            {
                method_signature signature{ method };
                auto& params = signature.params();
                params.resize(params.size() - 2);

                w.write(format,
                    type_name,
                    bind<write_consume_params>(signature),
                    bind<write_call_factory>(type, factory.type),
                    get_name(method),
                    bind<write_consume_args>(signature),
                    signature.params().empty() ? "" : ", ");
            }
        }
    }

    static void write_interface_override(writer& w, TypeDef const& type)
    {
        auto format = R"(    template <typename D>
    class %T
    {
        D& shim() noexcept { return *static_cast<D*>(this); }
        D const& shim() const noexcept { return *static_cast<const D*>(this); }
    public:
        using % = winrt::%;
%    };
)";

        for (auto&& [interface_name, info] : get_interfaces(w, type))
        {
            if (info.overridable && !info.base)
            {
                auto type_name = info.type.TypeName();

                w.write(format,
                    type_name,
                    type_name,
                    info.type,
                    bind_each<write_consume_declaration>(info.type.MethodList()));
            }
        }
    }

    static void write_class_override_usings(writer& w, get_interfaces_t const& required_interfaces)
    {
        std::map<std::string_view, std::set<std::string>> method_usage;

        for (auto&& [interface_name, info] : required_interfaces)
        {
            for (auto&& method : info.type.MethodList())
            {
                method_usage[get_name(method)].insert(interface_name);
            }
        }

        for (auto&& [method_name, interfaces] : method_usage)
        {
            if (interfaces.size() <= 1)
            {
                continue;
            }

            for (auto&& interface_name : interfaces)
            {
                w.write("        using impl::consume_t<D, %>::%;\n",
                    interface_name,
                    method_name);
            }
        }
    }

    static void write_class_override(writer& w, TypeDef const& type)
    {
        auto factories = get_factories(w, type);
        bool has_composable_factories{};

        for (auto&& [interface_name, factory] : factories)
        {
            if (factory.composable && !empty(factory.type.MethodList()))
            {
                has_composable_factories = true;
                break;
            }
        }

        if (!has_composable_factories)
        {
            return;
        }

        auto format = R"(    template <typename D, typename... Interfaces>
    struct %T :
        implements<D%, composing, Interfaces...>,
        impl::require<D%>,
        impl::base<D, %%>%
    {
        using composable = %;
    protected:
%%    };
)";

        auto type_name = type.TypeName();
        auto interfaces = get_interfaces(w, type);

        w.write(format,
            type_name,
            bind<write_class_override_implements>(interfaces),
            bind<write_class_override_requires>(interfaces),
            type_name,
            bind<write_class_override_bases>(type),
            bind<write_class_override_defaults>(interfaces),
            type_name,
            bind<write_class_override_constructors>(type, factories),
            bind<write_class_override_usings>(interfaces));
    }

    static void write_interface_requires(writer& w, TypeDef const& type)
    {
        auto interfaces = get_interfaces(w, type);

        if (interfaces.empty())
        {
            return;
        }

        w.write(",\n        impl::require<%", type);

        for (auto&& [name, info] : interfaces)
        {
            w.write(", %", name);
        }

        w.write('>');
    }

    static void write_interface_usings(writer& w, TypeDef const& type)
    {
        auto type_name = type.TypeName();
        auto interfaces_plus_self = get_interfaces(w, type);
        interfaces_plus_self.emplace_back(type_name, interface_info{ type });
        std::map<std::string_view, std::set<std::string>> method_usage;

        for (auto&& [interface_name, info] : interfaces_plus_self)
        {
            for (auto&& method : info.type.MethodList())
            {
                method_usage[get_name(method)].insert(interface_name);
            }
        }

        for (auto&& [method_name, interfaces] : method_usage)
        {
            if (interfaces.size() <= 1)
            {
                continue;
            }

            for (auto&& interface_name : interfaces)
            {
                w.write("        using impl::consume_t<%, %>::%;\n",
                    type_name,
                    interface_name,
                    method_name);
            }
        }
    }

    static void write_class_usings(writer& w, TypeDef const& type)
    {
        auto type_name = type.TypeName();
        auto default_interface = get_default_interface(type);
        auto default_interface_name = w.write_temp("%", default_interface);
        std::map<std::string_view, std::set<std::string>> method_usage;

        for (auto&& [interface_name, info] : get_interfaces(w, type))
        {
            if (info.defaulted && !info.base)
            {
                for (auto&& method : info.type.MethodList())
                {
                    method_usage[get_name(method)].insert(default_interface_name);
                }
            }
            else
            {
                for (auto&& method : info.type.MethodList())
                {
                    method_usage[get_name(method)].insert(interface_name);
                }
            }
        }

        for (auto&& [method_name, interfaces] : method_usage)
        {
            if (interfaces.size() <= 1)
            {
                continue;
            }

            for (auto&& interface_name : interfaces)
            {
                if (default_interface_name == interface_name)
                {
                    w.write("        using %::%;\n",
                        interface_name,
                        method_name);
                }
                else
                {
                    w.write("        using impl::consume_t<%, %>::%;\n",
                        type_name,
                        interface_name,
                        method_name);
                }
            }
        }
    }

    static void write_interface(writer& w, TypeDef const& type)
    {
        auto type_name = type.TypeName();
        auto generics = type.GenericParam();
        auto guard{ w.push_generic_params(generics) };

        if (empty(generics))
        {
            auto format = R"(    struct __declspec(empty_bases) % :
        Windows::Foundation::IInspectable,
        impl::consume_t<%>%
    {
        %(std::nullptr_t = nullptr) noexcept {}
        %(void* ptr, take_ownership_from_abi_t) noexcept : Windows::Foundation::IInspectable(ptr, take_ownership_from_abi) {}
        %(% const&) noexcept = default;
        %(%&&) noexcept = default;
        %& operator=(% const&) & noexcept = default;
        %& operator=(%&&) & noexcept = default;
%%    };
)";

            w.write(format,
                type_name,
                type_name,
                bind<write_interface_requires>(type),
                type_name,
                type_name,
                type_name, // %(T const&)
                type_name, // T(% const&)
                type_name, // %(T&&)
                type_name, // T(%&&)
                type_name, // %& operator=(T const&)
                type_name, // T& operator=(% const&)
                type_name, // %& operator=(T&&)
                type_name, // T& operator=(%&&)
                bind<write_interface_usings>(type),
                bind<write_interface_extensions>(type));
        }
        else
        {
            type_name = remove_tick(type_name);

            auto format = R"(    template <%>
    struct __declspec(empty_bases) % :
        Windows::Foundation::IInspectable,
        impl::consume_t<%>%
    {%
        %(std::nullptr_t = nullptr) noexcept {}
        %(void* ptr, take_ownership_from_abi_t) noexcept : Windows::Foundation::IInspectable(ptr, take_ownership_from_abi) {}
        %(% const&) noexcept = default;
        %(%&&) noexcept = default;
        %& operator=(% const&) & noexcept = default;
        %& operator=(%&&) & noexcept = default;
%%    };
)";

            w.write(format,
                bind<write_generic_typenames>(generics),
                type_name,
                type,
                bind<write_interface_requires>(type),
                bind<write_generic_asserts>(generics),
                type_name,
                type_name,
                type_name, // %(T const&)
                type_name, // T(% const&)
                type_name, // %(T&&)
                type_name, // T(%&&)
                type_name, // %& operator=(T const&)
                type_name, // T& operator=(% const&)
                type_name, // %& operator=(T&&)
                type_name, // T& operator=(%&&)
                bind<write_interface_usings>(type),
                bind<write_interface_extensions>(type));
        }
    }

    static void write_delegate(writer& w, TypeDef const& type)
    {
        auto generics = type.GenericParam();
        auto guard{ w.push_generic_params(generics) };
        auto type_name = type.TypeName();

        if (!empty(generics))
        {
            type_name = remove_tick(type_name);

            auto format = R"(    template <%>
)";

            w.write(format, bind<write_generic_typenames>(generics));
        }

        auto format = R"(    struct % : Windows::Foundation::IUnknown
    {%
        %(std::nullptr_t = nullptr) noexcept {}
        %(void* ptr, take_ownership_from_abi_t) noexcept : Windows::Foundation::IUnknown(ptr, take_ownership_from_abi) {}
        %(% const&) noexcept = default;
        %(%&&) noexcept = default;
        %& operator=(% const&) & noexcept = default;
        %& operator=(%&&) & noexcept = default;
        template <typename L> %(L lambda);
        template <typename F> %(F* function);
        template <typename O, typename M> %(O* object, M method);
        template <typename O, typename M> %(com_ptr<O>&& object, M method);
        template <typename O, typename M> %(weak_ref<O>&& object, M method);
        auto operator()(%) const;
    };
)";

        method_signature signature{ get_delegate_method(type) };

        w.write(format,
            type_name, // struct %
            bind<write_generic_asserts>(generics),
            type_name,
            type_name,
            type_name, // %(T const&)
            type_name, // T(% const&)
            type_name, // %(T&&)
            type_name, // T(%&&)
            type_name, // %& operator=(T const&)
            type_name, // T& operator=(% const&)
            type_name, // %& operator=(T&&)
            type_name, // T& operator=(%&&)
            type_name,
            type_name,
            type_name,
            type_name,
            type_name,
            bind<write_consume_params>(signature));
    }

    static void write_delegate_implementation(writer& w, TypeDef const& type)
    {
        auto format = R"(    template <typename H%> struct delegate<%, H> final : implements_delegate<%, H>
    {
        delegate(H&& handler) : implements_delegate<%, H>(std::forward<H>(handler)) {}

        int32_t __stdcall Invoke(%) noexcept final try
        {
%            %
            return 0;
        }
        catch (...) { return to_hresult(); }
    };
)";

        auto generics = type.GenericParam();
        auto guard{ w.push_generic_params(generics) };
        method_signature signature{ get_delegate_method(type) };

        w.write(format,
            bind<write_comma_generic_typenames>(generics),
            type,
            type,
            type,
            bind<write_abi_params>(signature),
            bind<write_produce_cleanup>(signature),
            bind<write_produce_upcall>("(*this)", signature));
    }

    static void write_delegate_definition(writer& w, TypeDef const& type)
    {
        auto generics = type.GenericParam();
        auto guard{ w.push_generic_params(generics) };
        auto type_name = type.TypeName();
        method_signature signature{ get_delegate_method(type) };

        if (!empty(generics))
        {
            auto format = R"(    template <%> template <typename L> %<%>::%(L handler) :
        %(impl::make_delegate<%<%>>(std::forward<L>(handler)))
    {
    }
    template <%> template <typename F> %<%>::%(F* handler) :
        %([=](auto&&... args) { return handler(args...); })
    {
    }
    template <%> template <typename O, typename M> %<%>::%(O* object, M method) :
        %([=](auto&&... args) { return ((*object).*(method))(args...); })
    {
    }
    template <%> template <typename O, typename M> %<%>::%(com_ptr<O>&& object, M method) :
        %([o = std::move(object), method](auto&&... args) { return ((*o).*(method))(args...); })
    {
    }
    template <%> template <typename O, typename M> %<%>::%(weak_ref<O>&& object, M method) :
        %([o = std::move(object), method](auto&&... args) { if (auto s = o.get()) { ((*s).*(method))(args...); } })
    {
    }
    template <%> auto %<%>::operator()(%) const
    {%
        check_hresult((*(impl::abi_t<%<%>>**)this)->Invoke(%));%
    }
)";

            type_name = remove_tick(type_name);

            w.write(format,
                bind<write_generic_typenames>(generics),
                type_name,
                bind_list(", ", generics),
                type_name,
                type_name,
                type_name,
                bind_list(", ", generics),
                bind<write_generic_typenames>(generics),
                type_name,
                bind_list(", ", generics),
                type_name,
                type_name,
                bind<write_generic_typenames>(generics),
                type_name,
                bind_list(", ", generics),
                type_name,
                type_name,
                bind<write_generic_typenames>(generics),
                type_name,
                bind_list(", ", generics),
                type_name,
                type_name,
                bind<write_generic_typenames>(generics),
                type_name,
                bind_list(", ", generics),
                type_name,
                type_name,
                bind<write_generic_typenames>(generics),
                type_name,
                bind_list(", ", generics),
                bind<write_consume_params>(signature),
                bind<write_consume_return_type>(signature, true),
                type_name,
                bind_list(", ", generics),
                bind<write_abi_args>(signature),
                bind<write_consume_return_statement>(signature));
        }
        else
        {
            auto format = R"(    template <typename L> %::%(L handler) :
        %(impl::make_delegate<%>(std::forward<L>(handler)))
    {
    }
    template <typename F> %::%(F* handler) :
        %([=](auto&&... args) { return handler(args...); })
    {
    }
    template <typename O, typename M> %::%(O* object, M method) :
        %([=](auto&&... args) { return ((*object).*(method))(args...); })
    {
    }
    template <typename O, typename M> %::%(com_ptr<O>&& object, M method) :
        %([o = std::move(object), method](auto&&... args) { return ((*o).*(method))(args...); })
    {
    }
    template <typename O, typename M> %::%(weak_ref<O>&& object, M method) :
        %([o = std::move(object), method](auto&&... args) { if (auto s = o.get()) { ((*s).*(method))(args...); } })
    {
    }
    inline auto %::operator()(%) const
    {%
        check_hresult((*(impl::abi_t<%>**)this)->Invoke(%));%
    }
)";

            w.write(format,
                type_name,
                type_name,
                type_name,
                type_name,
                type_name,
                type_name,
                type_name,
                type_name,
                type_name,
                type_name,
                type_name,
                type_name,
                type_name,
                type_name,
                type_name,
                type_name,
                type_name,
                bind<write_consume_params>(signature),
                bind<write_consume_return_type>(signature, true),
                type_name,
                bind<write_abi_args>(signature),
                bind<write_consume_return_statement>(signature));
        }
    }

    static void write_struct_field(writer& w, std::pair<std::string_view, std::string> const& field)
    {
        w.write("        @ %;\n",
            field.second,
            field.first);
    }

    static void write_struct_equality(writer& w, std::vector<std::pair<std::string_view, std::string>> const& fields)
    {
        for (size_t i = 0; i != fields.size(); ++i)
        {
            w.write(" left.% == right.%", fields[i].first, fields[i].first);

            if (i + 1 != fields.size())
            {
                w.write(" &&");
            }
        }
    }

    static bool write_structs(writer& w, std::vector<TypeDef> const& types)
    {
        auto format = R"(    struct %
    {
%    };
    inline bool operator==(% const& left, % const& right)%
    {
        return%;
    }
    inline bool operator!=(% const& left, % const& right)%
    {
        return !(left == right);
    }
)";

        if (types.empty())
        {
            return false;
        }

        struct complex_struct
        {
            complex_struct(writer& w, TypeDef const& type) :
                type(type),
                is_noexcept(!has_reference(type))
            {
                for (auto&& field : type.FieldList())
                {
                    fields.emplace_back(field.Name(), w.write_temp("%", field.Signature().Type()));
                }
            }

            static bool has_reference(TypeDef const&)
            {
                return false;
            };

            TypeDef type;
            std::vector<std::pair<std::string_view, std::string>> fields;
            bool is_noexcept{ false };
        };

        std::vector<complex_struct> structs;
        structs.reserve(types.size());

        for (auto&& type : types)
        {
            structs.emplace_back(w, type);
        }

        auto depends = [](writer& w, complex_struct const& left, complex_struct const& right)
        {
            for (auto&& field : left.fields)
            {
                if (w.write_temp("%", right.type) == field.second)
                {
                    return true;
                }
            }

            return false;
        };

        for (size_t left = 0; left < structs.size(); ++left)
        {
            for (size_t right = left + 1; right < structs.size(); ++right)
            {
                if (depends(w, structs[left], structs[right]))
                {
                    // Left depends on right, therefore move right in front of left.
                    complex_struct temp = std::move(structs[right]);
                    structs.erase(structs.begin() + right);
                    structs.insert(structs.begin() + left, std::move(temp));

                    // Start over from the newly inserted struct.
                    right = structs.size();
                    --left;
                }
            }
        }

        bool promote = false;
        auto cpp_namespace = w.write_temp("@", w.type_namespace);

        for (auto&& type : structs)
        {
            auto name = type.type.TypeName();
            std::string_view is_noexcept = type.is_noexcept ? " noexcept" : "";

            w.write(format,
                name,
                bind_each<write_struct_field>(type.fields),
                name,
                name,
                is_noexcept,
                bind<write_struct_equality>(type.fields),
                name,
                name,
                is_noexcept);

            for (auto&& field : type.fields)
            {
                if (field.second.find(':') == std::string::npos)
                {
                    continue;
                }

                if (!starts_with(field.second, cpp_namespace))
                {
                    promote = true;
                }
            }
        }

        return promote;
    }

    static void write_class_requires(writer& w, TypeDef const& type)
    {
        bool first{ true };

        for (auto&& [interface_name, info] : get_interfaces(w, type))
        {
            if (!info.defaulted || info.base)
            {
                if (first)
                {
                    first = false;
                    w.write(",\n        impl::require<%", type.TypeName());
                }

                w.write(", %", interface_name);
            }
        }

        if (!first)
        {
            w.write('>');
        }
    }

    static void write_fast_class_requires(writer& w, TypeDef const& type)
    {
        bool first{ true };

        for (auto&& [interface_name, info] : get_interfaces(w, type))
        {
            if (!info.exclusive)
            {
                if (first)
                {
                    first = false;
                    w.write(",\n        impl::require<%", type.TypeName());
                }

                w.write(", %", interface_name);
            }
        }

        if (!first)
        {
            w.write('>');
        }
    }

    static void write_class_base(writer& w, TypeDef const& type)
    {
        bool first{ true };

        for (auto&& base : get_bases(type))
        {
            if (first)
            {
                first = false;
                w.write(",\n        impl::base<%", type.TypeName());
            }

            w.write(", %", base);
        }

        if (!first)
        {
            w.write('>');
        }
    }

    static void write_fast_class_base_declarations(writer& w, TypeDef const& type)
    {
        for (auto&& base : get_bases(type))
        {
            auto format = R"(        operator impl::producer_ref<%> const() const noexcept;
)";

            w.write(format, base);

            for (auto&& [name, info] : get_interfaces(w, base))
            {
                if (!info.fastabi)
                {
                    break;
                }

                w.write_each<write_consume_declaration>(info.type.MethodList());
            }
        }
    }

    static void write_fast_class_base_definitions(writer& w, TypeDef const& type)
    {
        if (!has_fastabi(type))
        {
            return;
        }

        for (auto&& base : get_bases(type))
        {
            auto format = R"(    inline %::operator impl::producer_ref<%> const() const noexcept
    {
        return { (*(impl::abi_t<%>**)this)->base_%() };
    }
)";

            w.write(format, type.TypeName(), base, get_default_interface(type), base.TypeName());

            for (auto&& [name, info] : get_interfaces(w, base))
            {
                if (!info.fastabi)
                {
                    break;
                }

                w.write_each<write_consume_fast_base_definition>(info.type.MethodList(), type, base);
            }
        }
    }

    static void write_constructor_declarations(writer& w, TypeDef const& type, std::map<std::string, factory_info> const& factories)
    {
        auto type_name = type.TypeName();

        for (auto&& [factory_name, factory] : factories)
        {
            if (factory.activatable)
            {
                if (!factory.type)
                {
                    w.write("        %();\n", type_name);
                }
                else
                {
                    for (auto&& method : factory.type.MethodList())
                    {
                        method_signature signature{ method };

                        w.write("        %%(%);\n",
                            signature.params().size() == 1 ? "explicit " : "",
                            type_name,
                            bind<write_consume_params>(signature));
                    }
                }
            }
            else if (factory.composable && factory.visible)
            {
                for (auto&& method : factory.type.MethodList())
                {
                    method_signature signature{ method };
                    auto& params = signature.params();
                    params.resize(params.size() - 2);

                    w.write("        %%(%);\n",
                        signature.params().size() == 1 ? "explicit " : "",
                        type_name,
                        bind<write_consume_params>(signature));
                }
            }
        }
    }

    static void write_constructor_definition(writer& w, MethodDef const& method, TypeDef const& type, TypeDef const& factory)
    {
        auto type_name = type.TypeName();
        method_signature signature{ method };

        auto format = R"(    inline %::%(%) :
        %(%)
    {
    }
)";

        w.write(format,
            type_name,
            type_name,
            bind<write_consume_params>(signature),
            type_name,
            bind<write_optimized_call_factory>(type, factory, signature));
    }

    static void write_composable_constructor_definition(writer& w, MethodDef const& method, TypeDef const& type, TypeDef const& factory)
    {
        auto type_name = type.TypeName();
        method_signature signature{ method };
        auto& params = signature.params();
        auto inner_param = params.back().first.Name();
        params.pop_back();
        auto base_param = params.back().first.Name();
        params.pop_back();

        auto format = R"(    inline %::%(%)
    {
        Windows::Foundation::IInspectable %, %;
        *this = % { return f.%(%%%, %); });
    }
)";

        w.write(format,
            type_name,
            type_name,
            bind<write_consume_params>(signature),
            base_param,
            inner_param,
            bind<write_call_factory>(type, factory),
            get_name(method),
            bind<write_consume_args>(signature),
            params.empty() ? "" : ", ",
            base_param,
            inner_param);
    }


    static void write_static_declaration(writer& w, std::pair<std::string const, factory_info> const& factory, TypeDef const& type)
    {
        if (!factory.second.statics)
        {
            return;
        }

        for (auto&& method : factory.second.type.MethodList())
        {
            method_signature signature{ method };
            auto method_name = get_name(method);
            auto async_types_guard = w.push_async_types(signature.is_async());

            if (settings.component_opt && settings.component_filter.includes(type))
            {
                w.write("        %static % %(%);\n",
                    is_get_overload(method) ? "[[nodiscard]] " : "",
                    signature.return_signature(),
                    method_name,
                    bind<write_consume_params>(signature));
            }
            else
            {
                w.write("        %static auto %(%);\n",
                    is_get_overload(method) ? "[[nodiscard]] " : "",
                    method_name,
                    bind<write_consume_params>(signature));
            }

            if (is_add_overload(method))
            {
                auto format = R"(        using %_revoker = impl::factory_event_revoker<%, &impl::abi_t<%>::remove_%>;
        [[nodiscard]] static %_revoker %(auto_revoke_t, %);
)";

                w.write(format,
                    method_name,
                    factory.second.type,
                    factory.second.type,
                    method_name,
                    method_name,
                    method_name,
                    bind<write_consume_params>(signature));
            }
        }
    }

    static void write_static_definitions(writer& w, MethodDef const& method, TypeDef const& type, TypeDef const& factory)
    {
        auto type_name = type.TypeName();
        method_signature signature{ method };
        auto method_name = get_name(method);
        auto async_types_guard = w.push_async_types(signature.is_async());

        {
            auto format = R"(    inline auto %::%(%)
    {
        %%;
    }
)";

            w.write(format,
                type_name,
                method_name,
                bind<write_consume_params>(signature),
                signature.return_signature() ? "return " : "",
                bind<write_optimized_call_factory>(type, factory, signature));
        }

        if (is_add_overload(method))
        {
            auto format = R"(    inline %::%_revoker %::%(auto_revoke_t, %)
    {
        auto f = get_activation_factory<%, %>();
        return { f, f.%(%) };
    }
)";

            w.write(format,
                type_name,
                method_name,
                type_name,
                method_name,
                bind<write_consume_params>(signature),
                type_name,
                factory,
                method_name,
                bind<write_consume_args>(signature));
        }
    }

    static void write_class_definitions(writer& w, TypeDef const& type)
    {
        if (settings.component_opt && settings.component_filter.includes(type))
        {
            return;
        }

        auto type_name = type.TypeName();

        for (auto&& [interface_name, factory] : get_factories(w, type))
        {
            if (factory.activatable)
            {
                if (!factory.type)
                {
                    std::string_view format;

                    if (has_fastabi(type))
                    {
                        format = R"(    inline %::%() :
        %(impl::call_factory_cast<%(*)(Windows::Foundation::IActivationFactory const&), %>([](Windows::Foundation::IActivationFactory const& f) { return impl::fast_activate<%>(f); }))
    {
    }
)";
                    }
                    else
                    {
                        format = R"(    inline %::%() :
        %(impl::call_factory_cast<%(*)(Windows::Foundation::IActivationFactory const&), %>([](Windows::Foundation::IActivationFactory const& f) { return f.template ActivateInstance<%>(); }))
    {
    }
)";
                    }

                    w.write(format,
                        type_name,
                        type_name,
                        type_name,
                        type_name,
                        type_name,
                        type_name);
                }
                else
                {
                    w.write_each<write_constructor_definition>(factory.type.MethodList(), type, factory.type);
                }
            }
            else if (factory.composable && factory.visible)
            {
                w.write_each<write_composable_constructor_definition>(factory.type.MethodList(), type, factory.type);
            }
            else if (factory.statics)
            {
                w.write_each<write_static_definitions>(factory.type.MethodList(), type, factory.type);
            }
        }
    }

    static void write_slow_class(writer& w, TypeDef const& type, coded_index<TypeDefOrRef> const& base_type)
    {
        auto type_name = type.TypeName();
        auto factories = get_factories(w, type);

        auto format = R"(    struct __declspec(empty_bases) % : %%%
    {
        %(std::nullptr_t) noexcept {}
        %(void* ptr, take_ownership_from_abi_t) noexcept : %(ptr, take_ownership_from_abi) {}
%        %(% const&) noexcept = default;
        %(%&&) noexcept = default;
        %& operator=(% const&) & noexcept = default;
        %& operator=(%&&) & noexcept = default;
%%    };
)";

        w.write(format,
            type_name,
            base_type,
            bind<write_class_base>(type),
            bind<write_class_requires>(type),
            type_name,
            type_name,
            base_type,
            bind<write_constructor_declarations>(type, factories),
            type_name, // %(T const&)
            type_name, // T(% const&)
            type_name, // %(T%&)
            type_name, // T(%&&)
            type_name, // %& operator=(T const&)
            type_name, // T& operator=(% const&)
            type_name, // %& operator=(T&&)
            type_name, // T& operator=(%&&)
            bind<write_class_usings>(type),
            bind_each<write_static_declaration>(factories, type));
    }

    static void write_fast_class(writer& w, TypeDef const& type, coded_index<TypeDefOrRef> const& base_type)
    {
        auto type_name = type.TypeName();
        auto factories = get_factories(w, type);

        auto format = R"(    struct __declspec(empty_bases) % : %%
    {
        %(std::nullptr_t) noexcept {}
        %(void* ptr, take_ownership_from_abi_t) noexcept : %(ptr, take_ownership_from_abi) {}
%        %(% const&) noexcept = default;
        %(%&&) noexcept = default;
        %& operator=(% const&) & noexcept = default;
        %& operator=(%&&) & noexcept = default;
%%    };
)";

        w.write(format,
            type_name,
            base_type,
            bind<write_fast_class_requires>(type),
            type_name,
            type_name,
            base_type,
            bind<write_constructor_declarations>(type, factories),
            type_name, // %(T const&)
            type_name, // T(% const&)
            type_name, // %(T%&)
            type_name, // T(%&&)
            type_name, // %& operator=(T const&)
            type_name, // T& operator=(% const&)
            type_name, // %& operator=(T&&)
            type_name, // T& operator=(%&&)
            bind<write_fast_class_base_declarations>(type),
            bind_each<write_static_declaration>(factories, type));
    }

    static void write_static_class(writer& w, TypeDef const& type)
    {
        auto type_name = type.TypeName();
        auto factories = get_factories(w, type);

        auto format = R"(    struct %
    {
        %() = delete;
%    };
)";

        w.write(format,
            type_name,
            type_name,
            bind_each<write_static_declaration>(factories, type));
    }

    static void write_class(writer& w, TypeDef const& type)
    {
        if (auto default_interface = get_default_interface(type))
        {
            if (has_fastabi(type))
            {
                write_fast_class(w, type, default_interface);
            }
            else
            {
                write_slow_class(w, type, default_interface);
            }
        }
        else
        {
            write_static_class(w, type);
        }
    }

    static void write_std_hash(writer& w, TypeDef const& type)
    {
        auto generics = type.GenericParam();

        w.write("    template<%> struct hash<winrt::%> : winrt::impl::hash_base {};\n",
            bind<write_generic_typenames>(generics),
            type);
    }

    static void write_namespace_special(writer& w, std::string_view const& namespace_name, cache const& c)
    {
        if (namespace_name == "Windows.Foundation")
        {
            if (c.find("Windows.Foundation.PropertyValue"))
            {
                w.write(strings::base_reference_produce);
            }
            if (c.find("Windows.Foundation.Deferral"))
            {
                w.write(strings::base_deferral);
            }

            w.write(strings::base_coroutine_foundation);
        }
        else if (namespace_name == "Windows.Foundation.Collections")
        {
            w.write(strings::base_collections);
            w.write(strings::base_collections_base);
            w.write(strings::base_collections_input_iterable);
            w.write(strings::base_collections_input_vector_view);
            w.write(strings::base_collections_input_map_view);
            w.write(strings::base_collections_input_vector);
            w.write(strings::base_collections_input_map);
            w.write(strings::base_collections_vector);
            w.write(strings::base_collections_map);
        }
        else if (namespace_name == "Windows.System")
        {
            w.write(strings::base_coroutine_system);
        }
        else if (namespace_name == "Microsoft.System")
        {
            w.write(strings::base_coroutine_system_winui);
        }
        else if (namespace_name == "Windows.UI.Core")
        {
            w.write(strings::base_coroutine_ui_core);
        }
        else if (namespace_name == "Windows.UI.Xaml.Interop")
        {
            w.write(strings::base_xaml_typename);
        }
    }
}
